/*
 * @Author: Wangjun
 * @Date: 2023-06-29 08:55:27
 * @LastEditTime: 2023-09-21 13:25:45
 * @LastEditors: Wangjun
 * @Description:
 * @FilePath: \golib\cklog\cklog.go
 * hnxr
 */
package cklog

import (
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gitee.com/haodreams/godriver/clickhouse"
	"gitee.com/haodreams/golib/gensql"
	"gitee.com/haodreams/golib/logs"
	"gitee.com/haodreams/libs/easy"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type Logs struct {
	App      string
	Ts       easy.Time
	Level    int
	TraceID  string
	Msg      string
	UsedTime int32 //耗时
}

type CkLog struct {
	db     *gorm.DB
	queue  chan *Logs
	app    string
	closed bool
}

const maxRecord = 5000

var defaultLog = new(CkLog)

/**
 * @description: 初始化
 * @param {...string} dsn
 * @return {*}
 */
func Setup(dsn ...string) {
	defaultLog.Setup(dsn...)
}

/**
 * @description: 关闭写入ck
 * @return {*}
 */
func Close() {
	defaultLog.Close()
}

/**
 * @description: 设置数据库
 * @param {*gorm.DB} db
 * @return {*}
 */
func SetDB(db *gorm.DB) {
	defaultLog.SetDB(db)
}

/**
 * @description: 设置DSN
 * @param {string} dsn
 * @return {*}
 */
func SetDsn(dsn string) (err error) {
	return defaultLog.SetDsn(dsn)
}

/**
 * @description: 写入日志
 * @param {int} level
 * @param {string} traceID
 * @param {string} msg
 * @return {*}
 */
func Log(level int, traceID string, msg string, UsedTime ...int32) {
	defaultLog.WriteString(level, traceID, msg, UsedTime...)
}

// dsn=tcp://10.202.10.174:9000?database=hnxr&username=default&password=E35E00DF%23&compress=0
func (m *CkLog) Setup(dsn ...string) {
	m.queue = make(chan *Logs, maxRecord)
	binaryPath, _ := filepath.Abs(os.Args[0])
	name := filepath.Base(binaryPath)
	m.app = strings.TrimSuffix(name, filepath.Ext(name))
	if len(dsn) > 0 {
		m.SetDsn(dsn[0])
	}
	go m.work()
}

func (m *CkLog) Close() {
	m.closed = true
	m.queue <- nil
	logs.Info("停止写入CK 日志")
}

func (m *CkLog) work() {
	ok := true
	ls := make([]*Logs, 2000)
	idx := 0
	for ok {
		select {
		case l := <-m.queue:
			if l == nil {
				ok = false
				continue
			}
			ls[idx] = l
			idx++
		case <-time.After(time.Second * 2):
			if idx > 0 {
				m.InsertObjects(ls[:idx])
				idx = 0
			}
		}
		if idx >= 2000 {
			m.InsertObjects(ls[:idx])
			idx = 0
		}
	}
	if idx > 0 {
		m.InsertObjects(ls[:idx])
	}
}

func (m *CkLog) InsertObjects(rows interface{}) (count int, err error) {
	if m.db == nil {
		err = errors.New("没有有效的数据库对象")
		return
	}
	sql, records, err := gensql.GenSQLData(m.db, rows)
	if err != nil {
		return
	}

	if len(records) == 0 {
		return
	}

	count, err = m.Insert(sql, records)
	if err != nil { //再试一次
		count, err = m.Insert(sql, records)
		if err != nil {
			logs.Warn("日志写入ck失败,", err.Error())
		}
	}

	return
}

func (m *CkLog) Insert(sql string, rows [][]interface{}) (count int, err error) {
	db, err := m.db.DB()
	if err != nil {
		return
	}
	tx, err := db.Begin()
	if err != nil {
		logs.Error(sql, err)
		return
	}
	stmt, err := tx.Prepare(sql)
	if err != nil {
		tx.Rollback()
		logs.Error(err, sql)
		return
	}

	defer stmt.Close()

	for i := range rows {
		_, err = stmt.Exec(rows[i]...)
		if err != nil {
			logs.Warn(sql, "exec insert ", err)
			continue
		}
		count++
	}

	//logs.Infof("%s connect  parser to sql:%d used time:%s", msg, len(vals), time.Since(now))

	if err := tx.Commit(); err != nil {
		logs.Error(err)
	}
	return
}

func (m *CkLog) SetDB(db *gorm.DB) {
	m.db = db
}

func (m *CkLog) SetDsn(dsn string) (err error) {
	db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
	})
	if err != nil {
		return
	}

	d, err := db.DB()
	if err != nil {
		return
	}

	d.SetMaxIdleConns(1)
	d.SetMaxOpenConns(5)
	d.SetConnMaxLifetime(time.Second * 3600)
	d.SetConnMaxIdleTime(time.Second * 900)
	m.db = db
	return
}

func (m *CkLog) WriteString(level int, traceID string, msg string, UsedTime ...int32) {
	logs.Info(level, " Trace ID:", traceID, " Msg:", msg)
	if m.db == nil {
		return
	}

	if m.closed {
		logs.Warn("ck log is closed")
		return
	}

	l := new(Logs)
	l.Level = level
	l.TraceID = traceID
	l.Msg = msg
	if len(UsedTime) > 0 {
		l.UsedTime = UsedTime[0]
	}
	l.Ts = easy.Time(time.Now().Unix())
	l.App = m.app
	if len(m.queue) >= maxRecord {
		logs.Warn("ck log's queue is full")
		return
	}
	m.queue <- l
}
